package cos480.amiwsd;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

/**
 * 
 * GetMgmt:
 * 
 * The Get management class. This class provides methods for getting the IP addresses 
 * and landscape instances from the database. This is used by PSSTulator.
 * 
 * @author will, emma
 *
 */

public class GetMgmt {

	
	
	private static final int numHouseholds = 56556; //hmmmmmm........
	
	private BufferedWriter writer;
	
	private ArrayList<String> addrList;
	
	public GetMgmt() {
		this.writer = null;
	}
	
	/**
	 * Get a list of IP addresses that satisfy the given constraints
	 * Possible parameters:
	 * -- 
	 * 
	 * @param params list of constraint parameters and values for IP list
	 * @return
	 */
	public String getIPs(Statement sql, HashMap<String, String> params) throws SQLException {
		
		System.out.println("Getting IP address list... please be patient. This may take some time.");
		
		String value = "";
		
		ArrayList<String> out;
		
		String whereClause = " where ";
		
		//set hhNum to given value, or the default of 100.
		int hhNum = ((params.get("hhnum") != null) ? Integer.parseInt(params.get("hhnum")) : 100);
		
		//System.out.println("hhNum val: " + hhNum);
		String fileName = "";
		if (params.get("filename") != null) {
			fileName = params.get("filename");
		}
		else {
			fileName = "ipListDefaultFilename";
		}

		boolean neverScanned = false;
		//determine the status of if we want a scanned addresses or not
		if ((value = params.get("scanned")) != null) {
			String whenSc = this.whenScanned(value);
			if (whenSc != null) {
				whereClause += whenSc;
			} else {
				System.out.println("Generating IPs that have never been scanned...");
				
				if (this.setupFile(fileName)) {
					this.addrList = generateNewRandomIPs(sql, hhNum);
					
//					for (int l = 0; l < addrList.size(); l++)
//						System.out.println("IP: " + this.addrList.get(l));
					
					if (addrList == null) {
						System.out.println("Failed to generate list of never-scanned IPs.");
						return null;
					}
					this.writeNewIPsToFile(this.addrList, hhNum);
					neverScanned = true;
					this.closeFile();
					this.addrList = null;
					return null;
				}								
			}
						
		

			//consider other parameters... software type, version and result, 
			String otherParam = "";
			boolean software = false, version = false;
			if ((otherParam = params.get("software")) != null) {
				whereClause += " result like '%" + otherParam + "%' \n"; 
				software = true;
			}
			if (software) whereClause += " and ";
			if ((otherParam = params.get("version")) != null) {
				whereClause += " result like '%" + otherParam + "%' \n";
				version = true;
			}
			if (version) whereClause += " and ";
			if ((otherParam = params.get("success")) != null) {
			    System.out.println("successVal: " + otherParam);
				whereClause += " result_type = '" + otherParam + "' ";
			}
			
//			System.out.println("query: ");
//			System.out.println("select ip from resultofscan " + whereClause + " ip << '" + 192 + "." + 168 + ".0.0'::inet ");
			
			
			//generate list of already-scanned IP addresses:
			if (!neverScanned) this.addrList = this.generateOldRandomIPs(sql, whereClause, hhNum);
			
		}
		
		if (this.addrList != null) {

			
			if (this.setupFile(fileName)) {
			
				System.out.println("Finished generating IP address list, writing to file: " + params.get("filename"));
				this.writeNewIPsToFile(this.addrList, hhNum);
				this.addrList = null;
				this.closeFile();
			} else {
				System.err.println("Failed to open the file " + fileName);
			}
			
		} else {
			System.out.println("Failed to generate the IP address list.");
		}
	
		return whereClause;
	}
	
	
	private String genIntervalStatement(String interval) {
		
		String out = " (now() - ";
		
		System.out.println("Interval: " + interval);
		
		String[] intervals = interval.split("\\$");
		
		for (String s : intervals)
			System.out.println("Sval: " + s);
		 
		for (int i = 0; i < intervals.length; i++) {
			String[] splitInterval = intervals[i].split("&");	
			out += " interval '" + splitInterval[0] + " " + splitInterval[1] + "' " + (i < intervals.length - 1 ? " - " : ")");
		}
		//return " now() - interval '" + splitInterval[0] + " " + splitInterval[1] + "' ";	
		return out;
	}
	
	
	private String whenScanned(String when) {
		
		String whereClause = " ";
		
		System.out.println("when: \n");
		System.out.println(when);
		
		
		String[] typeAndTime = when.split("#");
		if(typeAndTime.length > 1) {
			
			
			if (typeAndTime[0].equals("before")) {
				//want ips scanned before typeAndTime[1]
				
				if (typeAndTime[1].contains("&")) {
					whereClause += " scan_date < " + this.genIntervalStatement(typeAndTime[1]);
				} else {
					whereClause += " scan_date < '" + typeAndTime[1] + "'::timestamp ";
				}
			} else if (typeAndTime[0].equals("after")) {
				//want ips scanned after typeAndTime[1]
				
				if (typeAndTime[1].contains("&")) {
					whereClause += " scan_date > " + this.genIntervalStatement(typeAndTime[1]);
				} else {
					whereClause += " scan_date > '" + typeAndTime[1] + "'::timestamp ";
				}
			} else {
				//want ips scanned between typeAndTime[0] and typeAndTime[1]
				//may contain an &... if it does, compute the intervals
				
				
				if (typeAndTime[0].contains("&")) {
					System.out.println("some things: " + typeAndTime[0]);
					//setup interval...
					//String[] numAndMag = typeAndTime[0].split("\\$");
					
//					for (String s : numAndMag)
//						System.out.println("sval: " + s);
					
					//for (int i = 0; i < numAndMag.length; i++) {
					whereClause += " scan_date > " + this.genIntervalStatement(typeAndTime[0]);// + (i < numAndMag.length - 1 ? " and " : "");
					//}
					
					
				} else {
					//concatenate a date...
				
					whereClause += " scan_date > '" + typeAndTime[0] + "'::timestamp ";
				}
				
				//whereClause += " and ";
				
				if (typeAndTime[1].contains("&")) {
					//setup interval...
					String[] numAndMag = typeAndTime[1].split("&");
					
					whereClause += " and scan_date < now() - interval '" + numAndMag[0] + " " + numAndMag[1] + "' ";
					
					
				} else {
					//concatenate a date...
					whereClause += " and scan_date > '" + typeAndTime[1] + "'::timestamp ";
				}
			}
			
		} else {
			//want IP addresses scanned on a specific date
			
			if (when.contains("never")) {
				return null;
			}
			else {
				whereClause += " scan_date = '" + typeAndTime[0] + "'::timestamp ";
			}
		}
		
		return whereClause;
	}
	
	private boolean setupFile(String fileName) {
		Charset charset = Charset.forName("US-ASCII"); //for compatibility with C
		
		BufferedWriter writer = null;
		try {
			writer =  new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName), charset));
		
			if (writer != null) {
				System.out.println("Succuessfully opened the file " + fileName);
				this.writer = writer;	
				return true;
			}
			
		} catch (Exception e) {
		    System.err.println("IOException... failed to open the bufferedwriter.");
		    System.out.println("Failed to open the file " + fileName);
		}
		
		return false;
	}
	
	private boolean closeFile() {
		
		try {
			this.writer.close();	
			return true;
		} catch (Exception e) {
			System.err.println("Failed to close the file.");
		}
		return false;		
	}
	
	
	
	private void writeNewIPsToFile(ArrayList<String> toPrint, int hhNum) {
		
		int index;
		
		
		for (int k = 0; k < hhNum; k++) {
			for (int i = 1; i < 224; i++) { //224
				for (int j = 0; j < 256; j++) { //256


					
				////////////////////////
				if (i == 169 && j == 254) {
					continue;
				}
				if (i == 172 && (j == 16)) {
					j = 32;
					continue;
				}
				////////////////////////
				if (i == 192) {
					if (j == 168) {
						continue;
					}
				}
				////////////////////////
				if (i == 198) {
					if (j == 18 || j == 19) {
						continue;
					}
				}
				
				index = i + (j * hhNum);
				
				try {
					this.writer.write(toPrint.get(index) + "\n");	
				} catch (Exception e) {
					System.err.println("Failed to write to the file.");
					e.printStackTrace();
				}
				
				
				}
			}
		}
		
		
		
	}
	
	
//	private void writeIPsToFile(String fileName, int hhNum) {
//		Charset charset = Charset.forName("US-ASCII"); //for compatibility with C
//		
//		System.out.println("In write method!");
//		
//		System.out.println("addrList size: " + this.addrList.size());
//		
//		BufferedWriter writer = null;
//		try {
//			writer =  new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName), charset));
//			
//		} catch (Exception e) {
//		    System.err.println("IOException... failed to open the bufferedwriter.");
//		}
//
//		if (writer != null) {
//			
//			System.out.println("Successful file openeing!");
//			
//			String s = "";
//			int index;
//			
//			System.out.println("size of addrList: " + this.addrList.size());
//			
//			
//			int numItters = (int) Math.ceil((double)this.addrList.size()/hhNum);
//			
//			for (int i = 0; i < numItters; i++) {
//				for (int j = 0; j < 256; j++) {
//					index = i + (j * hhNum);
//					
//					if (index < this.addrList.size()) {
//						s = this.addrList.get(index);
//						//System.out.println("addr: " + i);
//						try {
//								writer.write(s + "\n", 0, s.length() + 1);
//						} catch (Exception e) {
//								System.err.println("Failed to write the string "
//										       	+ s + " in writeIPsToFile for file " + fileName);
//						}
//					} else {
//					    continue;
//					}
//				}
//			}
//			try {
//				writer.close();	
//			} catch (Exception e) {
//				System.err.println("Failed to close the BufferedWriter in writeIPsToFile.");
//			}	
//		}
//	}
	
	
	/**
	 * generateOldRandomIPs:
	 * 
	 * This method generates a list of IP addresses that have been previously scanned, and fit the 
	 * given specification in the input query string.
	 * 
	 * @param sql Statement for executing queries.
	 * @param hhNum Number of individuals in a household.
	 * @param query The input query
	 * @return A list of IP addresses that match the given criteria.
	 * @throws SQLException
	 */
	
	private ArrayList<String> generateOldRandomIPs(Statement sql, String whereClause, int hhNum) throws SQLException {

		try {
			ResultSet numAlreadyScanned = sql
					.executeQuery("select count(1) from resultofscan "
							+ whereClause + " and ip << '199.199.0.0/16'::inet");
			if (numAlreadyScanned.next()) {
				if (numAlreadyScanned.getInt(1) <= hhNum) {
					System.out
							.println("There aren't "
									+ hhNum
									+ " unscanned individuals in any household. Unable to complete the list generation.");
					return null;
				}
			}
			numAlreadyScanned.close();
		} catch (SQLException e) {
			System.out.println("finished counting ");
		}

		ArrayList<String> out = new ArrayList<String>(224 * 255 * hhNum);

		String createView = "create view hhaddrs as (select ip from resultofscan "
				+ whereClause + ")";
		try {
			sql.executeUpdate(createView);
		} catch (SQLException e) {
			System.err
					.println("Failed to create hhaddrs view in generateOldRandomIPs.");
			System.err.println("here's the create view statement: \n");
			System.err.println(createView);
			throw e;
		}

		

		try {
			
			ResultSet rs = sql.executeQuery("select * from get_old_hosts("+hhNum+", 223, 255)");
			
			while (rs.next()) {
				out.add(rs.getInt(1) + "." + rs.getInt(2) + "." + rs.getInt(3) + "." + rs.getInt(4));
			}
			
			
		} catch (SQLException e) {
			System.err.println("Failed to get the random numbers.");
			sql.executeUpdate("drop view hhaddrs");
			throw e;
		}

		try {
			sql.executeUpdate("drop view hhaddrs");
		} catch (SQLException e) {
			System.err
					.println("Failed to drop the view hhaddrs in generateOldRandomIPs.");
			throw e;
		}

		return out;
	}
	/**
	 * generateNewRandomIPs:
	 * 
	 * This method generates a list of IP addresses that have never been previously scanned. 
	 * 
	 * 
	 * @param sql  
	 * @param hhNum
	 * @return
	 * @throws SQLException
	 */
	
	private ArrayList<String> generateNewRandomIPs(Statement sql, int hhNum) throws SQLException {
		
		ResultSet numAlreadyScanned = sql.executeQuery("select count(1) from host where ip << '199.199.0.0/16'::inet");
		if (numAlreadyScanned.next()) {
			if (65536 - numAlreadyScanned.getInt(1) <= hhNum) {
				System.out.println("There aren't " + hhNum + " unscanned individuals in any household. Unable to complete the list generation.");
				return null;
			}
		}
		
		numAlreadyScanned.close();
		
		
		ArrayList<String> temp = new ArrayList<String>(224 * 256 * hhNum);
		
		try {
			ResultSet rs = sql.executeQuery("select * from get_new_hosts("+hhNum+", 223, 255)");
			
			while (rs.next())
				temp.add(rs.getInt(1) + "." + rs.getInt(2) + "." + rs.getInt(3) + "." + rs.getInt(4));
			
			
		} catch (SQLException e) {
			System.err.println("Failed to execute the query for get_new_hosts().");
			e.printStackTrace();
			throw e;
		}
		
		return temp;
	}
	
}
	